using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace Kdg.Tools.FilesCopier
{
    public delegate void FilesCopierProcessorReportEvent(object sender, FilesCopierProcessorReportEventArgs e);
    public delegate void CopyReportEventHandler(object sender, FilesCopierProcessor.CopyReportEventArgs e);

    public partial class FilesCopierProcessor
    {
        public event ProgressChangedEventHandler Report;
        public event RunWorkerCompletedEventHandler Complete;

        private BackgroundWorker _worker;
        int bufferSize = 1024 * 10;

        private CopyCompleteEventArgs _copyCompleteArgs = null;
        public FilesCopierProcessor()
        {
            _worker = new BackgroundWorker();
            _worker.ProgressChanged += new ProgressChangedEventHandler(_worker_ProgressChanged);
            _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted);
            _worker.DoWork += new DoWorkEventHandler(_worker_DoWork);

            _worker.WorkerSupportsCancellation = true;
            _worker.WorkerReportsProgress = true;

        }

        public void CancelAsync()
        {
            _worker.CancelAsync();
        }

        void _worker_DoWork(object sender, DoWorkEventArgs e)
        {
            RunParams _params = e.Argument as RunParams;
            _copyCompleteArgs = new CopyCompleteEventArgs();
            ProcessDirectory(_params);
        }

        private void ProcessDirectory(RunParams _params)
        {
            DirectoryInfo source = new DirectoryInfo(_params.Source);
            foreach (DirectoryInfo di in source.GetDirectories())
            {
                if (_worker.CancellationPending)
                {
                    _copyCompleteArgs.Canceled = true;
                    return;
                }

                DirectoryInfo diDest = new DirectoryInfo(_params.Destination + "\\" + di.Name);
                diDest.Create();
                RunParams rp = new RunParams(_params);
                rp.Destination = diDest.FullName;
                rp.Source = di.FullName;
                ProcessDirectory(rp);
            }

            foreach (FileInfo fi in source.GetFiles())
            {
                if (_worker.CancellationPending)
                {
                    _copyCompleteArgs.Canceled = true;
                    return;
                }

                if (CheckFilenameByMasksList(fi, _params.IncludeMask, _params.ExcludeMask))
                    CopyFile(fi, _params.Destination);
                else
                {
                    CopyReportEventArgs crea = new CopyReportEventArgs(
                        fi.Name,
                        fi.Directory.FullName,
                        string.Format("File '{0}' skipped by one mask.\r\n", fi.Name));
                    _worker.ReportProgress(0, crea);
                }
            }
        }

        private void CopyFile(FileInfo fi, string destFolder)
        {
            StreamReader sr = null;
            StreamWriter sw = null;
            CopyReportEventArgs crea = new CopyReportEventArgs(fi.Name, fi.Directory.FullName, destFolder, fi.Length, 0);
            try
            {
                sr = new StreamReader(fi.FullName, Encoding.Default);
                sw = new StreamWriter(destFolder + "\\" + fi.Name, false, Encoding.Default);

                char[] buffer = new char[bufferSize];
                int readBytes = 1;
                long totalReadBytes = 0;
                DateTime timeStart = DateTime.Now;
                DateTime timeEnd = DateTime.Now;
                while (readBytes > 0)
                {
                    if (_worker.CancellationPending)
                    {
                        _copyCompleteArgs.Canceled = true;
                        return;
                    }

                    timeStart = DateTime.Now;

                    readBytes = sr.ReadBlock(buffer, 0, bufferSize);
                    sw.Write(buffer, 0, readBytes);
                    totalReadBytes += readBytes;
                    _copyCompleteArgs.BytesCopied += readBytes;

                    timeEnd = DateTime.Now;

                    crea.BytesCopied = totalReadBytes;
                    crea.Speed = ((double)readBytes) / ((TimeSpan)(timeEnd - timeStart)).TotalSeconds;
                    _worker.ReportProgress(0, crea);

                }
                _copyCompleteArgs.FilesCopied++;
            }
            catch (Exception ex)
            {
                if (crea != null)
                {
                    crea.Exception = ex;
                    _worker.ReportProgress(0, crea);
                }
            }
            finally
            {
                if (sr != null)
                    sr.Close();
                if (sw != null)
                    sw.Close();
            }
        }

        private static bool CheckFilenameByMasksList(FileInfo file, List<string> _incMasks, List<string> _excMasks)
        {
            foreach (string mask in _incMasks)
                if (CheckFilenameByMask(file.Name, mask) == false)
                    return false;
            foreach (string mask in _excMasks)
                if (CheckFilenameByMask(file.Name, mask) == true)
                    return false;
            return true;
        }

        private static bool CheckFilenameByMask(string fileName, string mask)
        {
            string regEx = "^";
            fileName = fileName.ToLower();
            foreach (char cm in mask)
            {
                if (cm != '*' && cm != '?')
                    regEx += cm;
                else
                    regEx += "(\\w)" + cm + "?";
            }
            regEx += '$';
            Regex re = new Regex(regEx);
            MatchCollection mc = re.Matches(fileName);
            if (mc.Count > 0)
                return true;

            return false;
        }

        void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            _copyCompleteArgs.Complete();

            RunWorkerCompletedEventArgs rwc = new RunWorkerCompletedEventArgs(
                _copyCompleteArgs,
                e.Error,
                e.Cancelled);
            if (Complete != null)
                Complete(sender, rwc);
        }

        void _worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (Report != null)
                Report(sender, e);
        }

        public void StartProcess(RunParams Params)
        {
            _worker.RunWorkerAsync(Params);
        }
    }
}
